﻿using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace Library.Winform.Utils {
  public class ImageUtil {
    public static bool GetBoundingBoxForChanges(Bitmap prevBitmap, Bitmap newBitmap, out Rectangle diff) {
      if (prevBitmap.Width != newBitmap.Width ||
          prevBitmap.Height != newBitmap.Height ||
          prevBitmap.PixelFormat != newBitmap.PixelFormat) {
        // Not the same shape...can't do the search.
        //
        diff = Rectangle.Empty;
        return false;
      }

      // Init the search parameters.
      //
      int width = newBitmap.Width;
      int height = newBitmap.Height;
      int left = width;
      int right = 0;
      int top = height;
      int bottom = 0;

      BitmapData bmNewData = null;
      BitmapData bmPrevData = null;
      try {
        // Lock the bits into memory.
        //
        bmNewData = newBitmap.LockBits(
          new Rectangle(0, 0, newBitmap.Width, newBitmap.Height),
          ImageLockMode.ReadOnly, newBitmap.PixelFormat);
        bmPrevData = prevBitmap.LockBits(
          new Rectangle(0, 0, prevBitmap.Width, prevBitmap.Height),
          ImageLockMode.ReadOnly, prevBitmap.PixelFormat);

        // The images are ARGB (4 bytes)
        //
        const int numBytesPerPixel = 4;

        // Get the number of integers (4 bytes) in each row
        //    of the image.
        //
        int strideNew = bmNewData.Stride / numBytesPerPixel;
        int stridePrev = bmPrevData.Stride / numBytesPerPixel;

        // Get a pointer to the first pixel.
        //
        // Note: Another speed up implemented is that I don't
        //    need the ARGB elements. I am only trying to detect
        //    change. So this algorithm reads the 4 bytes as an
        //    integer and compares the two numbers.
        //
        IntPtr scanNew0 = bmNewData.Scan0;
        IntPtr scanPrev0 = bmPrevData.Scan0;

        // Enter the unsafe code.
        //
        unsafe {
          // Cast the safe pointers into unsafe pointers.
          //
          var pNew = (int*)(void*)scanNew0;
          var pPrev = (int*)(void*)scanPrev0;

          // First Pass - Find the left and top bounds
          //    of the minimum bounding rectangle. Adapt the
          //    number of pixels scanned from left to right so
          //    we only scan up to the current bound. We also
          //    initialize the bottom & right. This helps optimize
          //    the second pass.
          //
          // For all rows of pixels (top to bottom)
          //
          for (int y = 0; y < newBitmap.Height; ++y) {
            // For pixels up to the current bound (left to right)
            //
            for (int x = 0; x < left; ++x) {
              // Use pointer arithmetic to index the
              //    next pixel in this row.
              //
              if ((pNew + x)[0] != (pPrev + x)[0]) {
                // Found a change.
                //
                if (x < left) {
                  left = x;
                }
                if (x > right) {
                  right = x;
                }
                if (y < top) {
                  top = y;
                }
                if (y > bottom) {
                  bottom = y;
                }
              }
            }

            // Move the pointers to the next row.
            //
            pNew += strideNew;
            pPrev += stridePrev;
          }

          // If we did not find any changed pixels
          //    then no need to do a second pass.
          //
          if (left != width) {
            // Second Pass - The first pass found at
            //    least one different pixel and has set
            //    the left & top bounds. In addition, the
            //    right & bottom bounds have been initialized.
            //    Adapt the number of pixels scanned from right
            //    to left so we only scan up to the current bound.
            //    In addition, there is no need to scan past
            //    the top bound.
            //

            // Set the pointers to the first element of the
            //    bottom row.
            //
            pNew = (int*)(void*)scanNew0;
            pPrev = (int*)(void*)scanPrev0;
            pNew += (newBitmap.Height - 1) * strideNew;
            pPrev += (prevBitmap.Height - 1) * stridePrev;

            // For each row (bottom to top)
            //
            for (int y = newBitmap.Height - 1; y > top; y--) {
              // For each column (right to left)
              //
              for (int x = newBitmap.Width - 1; x > right; x--) {
                // Use pointer arithmetic to index the
                //    next pixel in this row.
                //
                if ((pNew + x)[0] != (pPrev + x)[0]) {
                  // Found a change.
                  //
                  if (x > right) {
                    right = x;
                  }
                  if (y > bottom) {
                    bottom = y;
                  }
                }
              }

              // Move up one row.
              //
              pNew -= strideNew;
              pPrev -= stridePrev;
            }
          }
        }
      } catch {
      } finally {
        // Unlock the bits of the image.
        //
        if (bmNewData != null) {
          newBitmap.UnlockBits(bmNewData);
        }
        if (bmPrevData != null) {
          prevBitmap.UnlockBits(bmPrevData);
        }
      }

      // Validate we found a bounding box. If not
      //    return an empty rectangle.
      //
      int diffImgWidth = right - left + 1;
      int diffImgHeight = bottom - top + 1;
      if (diffImgHeight < 0 || diffImgWidth < 0) {
        // Nothing changed
        diff = Rectangle.Empty;
        return true;
      }

      // Return the bounding box.
      //
      diff = new Rectangle(left - 50, top - 50, diffImgWidth + 50, diffImgHeight + 50);
      return true;
    }
  }
}
